//==============================================================================
//
//           DGZipFile - the gzip file class in the ofc-library
//
//               Copyright (C) 2005  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2007-04-04 18:07:14 $ $Revision: 1.7 $
//
//==============================================================================

#include "ofc/DGZipFile.h"

#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

#ifdef HAVE_DGZIPFILE
#include <zlib.h>
#endif

#include "ofc/DDatable.h"


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DText.h"
#include "ofc/DData.h"
#include "ofc/DList.h"
#include "ofc/DTextReadable.h"
#include "ofc/DTextWritable.h"
#include "ofc/DDataReadable.h"
#include "ofc/DDataWritable.h"

#if defined(HAVE_LIBZ) && defined(HAVE_ZLIB_H)
#define HAVE_DGZIPFILE
#endif

#ifdef HAVE_DGZIPFILE

// the seek origins
#define DGZ_SEEK_SET  (0)    // Seek from the start of the file
#define DGZ_SEEK_CUR  (1)    // Seek from the current position in the file

// the strategies
#define DGZ_DEFAULT   (0)    // Default stategy
#define DGZ_FILTERED  (1)    // Filtered data strategy
#define DGZ_HUFFMAN   (2)    // Only huffman compression strategy


// The DGZipFile class implements a number of methods for opening of, writing to, 
// reading from and closing of gzip files.

@interface DGZipFile : Object <DTextReadable,DTextWritable,DDataReadable,DDataWritable>
{
@private
  void           *_file;    // the file pointer
}

#endif
#endif

#ifdef HAVE_DGZIPFILE

@implementation DGZipFile


//// Constructors

//
// Initialise an empty file object
//
// @return the object
//

- (DGZipFile *) init
{
  [super init];

  _file  = NULL;
  
  return self;
}

//
// Open a gzip file with default level and strategy
//
// @param name     the filename (with extension .gz)
// @param mode     the file mode (r,w)
//
// @return the object
//

- (DGZipFile *) init :(const char *) name :(const char *) mode
{
  [self init];

  [self open :name :mode];
  
  return self;
}

//
// Open a gzip file
//
// @param name     the filename (with extension .gz)
// @param mode     the file mode (r,w)
// @param level    the level of compression
// @param strategy the strategy of compression
//
// @return the object
//

- (DGZipFile *) init :(const char *) name :(const char *) mode :(int) level :(int) strategy
{
  [self init];

  [self open :name :mode :level :strategy];
  
  return self;
}


//// Copy related methods

//
// Do a shallow copy of the object (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");

  return [super shallowCopy];
}


//// Deconstructor

//
// Free the object
//
// @return the file object
//

- free
{
  if (_file != NULL)
  {
    [self close];
  }
  
  [super free];

  return self;
}


//// File info methods

//
// Check if the file is open
//
// @return is the file open ?
//

- (BOOL) isOpen
{
  return (_file != NULL);
}

//
// Return the last error
//
// @return the last error
//

- (int) error
{
  int error = errno;
  
  if (_file != NULL)
  {
    gzerror(_file, &error);
    
    if (error == Z_ERRNO)
    {
      error = errno;
    }
  }

  return error;
}


//// File opening methods

//
// Open a gzip file with default level and strategy
//
// @param name     the filename (with extension .gz)
// @param mode     the file mode (r,w)
//
// @return is the file open ?
//

- (BOOL) open :(const char *) name :(const char *) mode
{
  if (_file != NULL)
  {
    [self close];
  }
  
  _file = gzopen(name, mode);

  return (_file != NULL);
}

//
// Open a gzip file with a level and strategy (note: only output files)
//
// @param name     the filename (with extension .gz)
// @param mode     the file mode (w)
// @param level    the level of compression (0 = none..9 = max,slow)
// @param strategy the strategy of compression (see DGZ..)
//
// @return is the file open ?
//

- (BOOL) open :(const char *) name :(const char *) mode :(int) level :(int) strategy
{
  BOOL ok = YES;
  
  switch (strategy)
  {
  case DGZ_DEFAULT  : strategy = Z_DEFAULT_STRATEGY; break;
  case DGZ_FILTERED : strategy = Z_FILTERED;         break;
  case DGZ_HUFFMAN  : strategy = Z_HUFFMAN_ONLY;     break;
  default           :
    WARNING(DW_INVALID_ARG, "strategy");
    ok = NO;
    break;
  }
  
  if ((level < 0) && (level > 9))
  {
    WARNING(DW_INVALID_ARG, "level");
    ok = NO;
  }
  
  if (ok)
  {
    if (_file != NULL)
    {
      [self close];
    }
  
    _file = gzopen(name, mode);

    ok = (_file != NULL);

    if (ok)
    {
      gzsetparams(_file, level, strategy);
    }
  }
  
  return (ok);
}


//// TextReadable protocol implementation

//
// Read a text string (all available text)
//
// @return a (new) text string
//

- (DText *) readText
{
  DText *string = [DText alloc];

  [string init];

  if (_file != NULL)
  {
    const
    int   length = 2048;
    char  buffer[length];
    char *result;
     
    while (!gzeof(_file))
    {
      result = gzgets(_file, buffer, length);
      
      if (result != NULL)
      {
        [string append :buffer];
      }
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");
    
  return string;
}

//
// Read a text string 
//
// @param length   the length of the text to be read
//
// @return a (new) text string
//

- (DText *) readText :(long) length
{
  DText *string = [DText alloc];

  [string init];

  if ((_file != NULL) && (length > 0))
  {
    const int  size = 2048;
    char       buffer[size];
    char      *result;
    long       total = 0;
    long       part  = size;
    
    while ((!gzeof(_file)) && (total < size))
    {
      if ((length - total) < part)
      {
        part = length - total - 1;
      }

      result = gzgets(_file, buffer, part);

      if (result != NULL)
      {
        [string append :buffer];

        total += strlen(buffer);
      }
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return string;
}  

//
// Read a line of text (till Eof or \n)
//
// @return a (new) text string (or nil for no data)
//

- (DText *) readLine;
{
  DText *string = nil;
  
  if (_file != NULL)
  {
    int ch = gzgetc(_file);

    if (ch != EOF)
    {
      string = [DText alloc];

      [string init];

      while ((ch != '\n') && (ch != EOF))
      {
        [string push :ch];

        ch = gzgetc(_file);
      }
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");
  
  return string;
}

//
// Read a character
//
// @return the character read (EOS for EOF)
//

- (char) readChar;
{
  char ch = EOS;

  if (_file != NULL)
  {
    ch = gzgetc(_file);

    if (ch == EOF) ch = EOS;
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return ch;
}

//
// Tell the current position in the readable
//
// @return the current position (or -1 for error)
//

- (unsigned long) tell;
{
  return (_file != NULL) ? gztell(_file) : -1;
}

//
// Move the current position in the readable
//
// @param offset   the offset from the origin (in bytes)
// @param origin   the origin for the offset (see DGZ_SEEK..)
//
// @return success
//

- (BOOL) seek :(unsigned long) offset :(int) origin;
{
  BOOL ok = NO;
  
  if ((origin != DGZ_SEEK_SET) && (origin != DGZ_SEEK_CUR))
  {
    WARNING(DW_INVALID_ARG, "origin");
  }
  else if (_file != NULL)
  {
    ok = (gzseek(_file, offset, origin) != -1);
  }
  
  return ok;
}

//
// Skip a number of positions
//
// @param offset   the number of bytes to skip
//
// @return success
//

- (BOOL) skip :(unsigned long) offset;
{
  return (_file != NULL) ? (gzseek(_file, offset, SEEK_SET) != -1) : NO; 
}

//
// Check if the end of the gzip file is reached
//
// @return is it?
//

- (BOOL) isEof;
{
  return (_file != NULL) ? (gzeof(_file)) : YES;
}


//// Implementation of DTextWritable

//
// Write text string 
//
// @param text     the text to be written
//
// @return success
//

- (BOOL) writeText :(const char *) text;
{
  return (_file != NULL) ? (gzputs(_file, text) != EOF) : NO;
}

//
// Write line (appending a '\n')
//
// @param text     the text to be written
//
// @return success
//

- (BOOL) writeLine :(const char *) text
{
  BOOL ok = (_file != NULL);
  
  if (ok)
  {
    ok &= (gzputs(_file, text) != EOF);
    ok &= (gzputc(_file, '\n') != EOF);
  }

  return ok;
}

//
// Write a character
//
// @param ch       the character to be written
//
// @return success
//

- (BOOL) writeChar :(char) ch;
{
  return (_file != NULL) ? (gzputc(_file, ch) != EOF) : NO;
}


//// Methods for DDataReadable protocol

//
// Read a data string 
//
// @param length   the length of the data
//
// @return the object
//

- (DData *) readData :(unsigned long) length;
{
  DData *string = [DData alloc];

  [string init];
  if ((_file != NULL) && (length > 0))
  {
    const int     size  = 2048;
    unsigned char buffer[size];
    unsigned long total = 0;
    unsigned long part  = size;
    unsigned long result;
    
    while ((!gzeof(_file)) && (total < length))
    {
      if ((length - total) < part)
      {
        part = length - total;
      }
      
      result = gzread(_file, buffer, sizeof(buffer[0]) * part);
      
      if (result > 0)
      {
        [string append :buffer :result];

        total += result;
      }
    }
  }

  return string;
}


//
// Read a data string 
//
// @param dest     the destination buffer
// @param length   the length of the data (and minimum size of dest)
//
// @return the actual number of bytes read
//
- (unsigned long) readData :(unsigned char *) dest :(unsigned long) length
{
  unsigned long result = 0;
  
  if (dest == NULL)
  {
    WARNING(DW_INVALID_ARG, "dest");
  }
  else if ((_file != NULL) && (length > 0))
  {
    if (!gzeof(_file))
    {
      result = gzread(_file, dest, sizeof(dest[0]) * length);
    }
  }

  return result;
}


//
// Read a byte
//
// @return the byte read (0 for error)
//
- (unsigned char) readByte;
{
  unsigned char ch = 0;

  if (_file != NULL)
  {
    if (gzread(_file, &ch, sizeof(ch)) < 1)
    {
      ch = 0;
    }
  }

  return ch;
}

//
// Read a short
//
// @return the short
//

- (short) readShort;
{
  short nr = 0;

  if (_file != NULL)
  {
    if (gzread(_file, &nr, sizeof(nr)) < 1)
    {
      nr = 0;
    }
  }

  return nr;
}

//
// Read a long
//
// @return the long
//

- (long) readLong;
{
  long nr = 0;

  if (_file != NULL)
  {
    if (gzread(_file, &nr, sizeof(nr)) < 1)
    {
      nr = 0;
    }
  }

  return nr;
}

//
// Read a double
//
// @return the double
//

- (double) readDouble;
{
  double nr = 0.0;

  if (_file != NULL)
  {
    if (gzread(_file, &nr, sizeof(nr)) < 1)
    {
      nr = 0.0;
    }
  }

  return nr;
}


//// DDataWritable protocol implementation

//
// Write a data string 
//
// @param data     the data to be written
// @param length   the length of the data
//
// @return success
//

- (BOOL) writeData :(const unsigned char *) text :(unsigned long) length;
{
  BOOL ok = NO;

  if (_file != NULL)
  {
    ok = (gzwrite(_file, (void *) text, sizeof(unsigned char) * length) == (length * sizeof(unsigned char)));
  }

  return ok;
}
  
//
// Write a byte
//
// @param byte     the byte to be written
//
// @return success
//

- (BOOL) writeByte :(unsigned char) byte;
{
  BOOL ok = NO;

  if (_file != NULL)
  {
    ok = (gzwrite(_file, &byte, sizeof(unsigned char)) == sizeof(unsigned char));
  }

  return ok;
}
 
//
// Write a short 
//
// @param nr       the short to be written
//
// @return success
//

- (BOOL) writeShort :(short) nr;
{
  BOOL ok = NO;

  if (_file != NULL)
  {
    ok = (gzwrite(_file, &nr, sizeof(nr)) == sizeof(nr));
  }

  return ok;
}
 
//
// Write a long
//
// @param nr       the long to be written
//
// @return success
//

- (BOOL) writeLong :(long) nr
{
  BOOL ok = NO;

  if (_file != NULL)
  {
    ok = (gzwrite(_file, &nr, sizeof(nr)) == sizeof(nr));
  }

  return ok;
}
 
//
// Write a double
//
// @param nr       the double to be written
//
// @return success
//

- (BOOL) writeDouble :(double) nr
{
  BOOL ok = NO;

  if (_file != NULL)
  {
    ok = (gzwrite(_file, &nr, sizeof(nr)) == sizeof(nr));
  }
  return ok;
}


//// List related methods

//
// Read all lines from a file into a list of strings
//
// @return a (new) list of (new) DText objects
//

- (DList *) readLines
{
  DList   *list = [DList alloc];
 
  [list init];
 
  if (_file != NULL)
  {
    [self seek :0 :0];
    while (!gzeof(_file))
    {
      DText *line = [self readLine];

      if (line != nil)
      {
        [list append :line];
      }
    }
  }

  return list;
}

//
// Writes all the elements ('lines') of text from a list to file
//
// @param list     the list with objects that responds to textable protocol
//
// @return success
//

- (BOOL) writeLines :(DList *) list
{
  BOOL success = YES;
  
  if (list != NULL)
  {
    DListIterator *iter  = [DListIterator alloc];
    id             object;

    [iter init :list];

    object = [iter first];            
    while ((object != nil) && (success))
    {
      if ([object conformsTo :@protocol(DTextable)])
      {
        DText *string = [object toText];
  
        success = [self writeLine :[string cstring]];

        [string free];
      }
      
      object = [iter next];
    }
    
    [iter free];
  }
  
  return success;
}


//// File manipulation

//
// Flush the output buffers of the file
//
// @return success
//

- (BOOL) flush
{
  return (_file != NULL) ? (gzflush(_file, Z_SYNC_FLUSH) != -1) : NO;
}


//// File closing methods

//
// Close the file
//
// @return the file object
//

- (DGZipFile *) close
{
  if (_file != NULL)
  {
    gzclose(_file);

    _file = NULL;
  }

  return self;
}

@end

#endif

/*===========================================================================*/

